﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Scripting.Utils;
using Scala.Compiler.Ast;
using Scala.Compiler.Binding;

namespace Scala.Compiler
{
    public class ScalaBinder : ScalaWalker
    {
        private ScalaAst _globalAst;
        internal ScalaScope _currentScope;
        private List<ScalaScope> _scopes = new List<ScalaScope>();

        private ScalaBinder(ScalaAst ast)
        {
            ContractUtils.RequiresNotNull(ast, "ast");

            _globalAst = ast;
        }

        #region Public Surface

        internal static void BindAst(ScalaAst ast)
        {
            Assert.NotNull(ast);
            ScalaBinder binder = new ScalaBinder(ast);
            binder.Bind(ast);
        }

        #endregion

        private void Bind(ScalaAst unboundAst)
        {
            Assert.NotNull(unboundAst);
         
            unboundAst.Walk(this);


        }

        #region AstBinder Overrides

        public override bool Walk(PackageDefStatement node)
        {
            if (_currentScope != null)
            {
                _currentScope.ChildScopes.Add(node.PId, node.Scope);
            }
            node.Scope.ParentScope = _currentScope;
            _currentScope = node.Scope;

            foreach (var stat in node.Statements)
            {
                stat.Walk(this);
            }
            return false;
        }

        public override void PostWalk(PackageDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(ModuleDefStatement node)
        {
            _currentScope.ChildScopes.Add(node.Name, node.Scope);
            
            // Adding the Type to the ParentScope
            var tp = _currentScope.EnsureType(node.Name, node);
            
            tp.ScalaScope = node.Scope;

            node.ScalaType = tp;
            node.Scope.ParentScope = _currentScope;
            _currentScope = node.Scope;
            node.TypeName = node.Name;
            node.Scope.CurrentType = tp;
            node.Body.Walk(this);
            return false;
        }

        public override void PostWalk(ModuleDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(ClassDefStatement node)
        {
            _currentScope.ChildScopes.Add(node.Name, node.Scope);

            // Adding the Type to the ParentScope
            var tp = _currentScope.EnsureType(node.Name, node);

            tp.ScalaScope = node.Scope;

            node.ScalaType = tp;
            node.Scope.ParentScope = _currentScope;
            _currentScope = node.Scope;
            node.TypeName = node.Name;
            node.Scope.CurrentType = tp;
            node.Body.Walk(this);
            return false;
        }

        public override void PostWalk(ClassDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(DefDefStatement node)
        {
            _currentScope.ChildScopes.Add(node.Name, node.Scope);

            // Adding the Function to the ParentScope
            var func = _currentScope.EnsureFunction(node.Name, node);

            node.ScalaFunction = func;
            node.Scope.ParentScope = _currentScope;
            _currentScope = node.Scope;
            node.Scope.CurrentType = node.Scope.ParentScope.CurrentType;

            foreach (var p in node.AllParams)
            {
                p.Walk(this);
            }

            node.Body.Walk(this);
            if (node.TypeName == null)
            {
                node.TypeName = node.Body.TypeName;
            }
            return false;
        }

        public override void PostWalk(DefDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(ValDefStatement node)
        {
            node.Scope = _currentScope;
            var variable = _currentScope.EnsureVariable(node.Name,node);
            if (_currentScope.IsClass || _currentScope.IsModule)
            {
                variable.IsProperty = true;
            }
            else
            {
                variable.IsProperty = false;
            }
            node.ScalaVariable = variable;

            node.Rhs.Walk(this);
            if (node.Rhs.TypeName != null)
            {
                if (node.TypeName != null)
                {
                    if (node.TypeName != node.Rhs.TypeName) throw new Exception(String.Format("Exprected Type of val is {0}, found type {1}", node.TypeName, node.Rhs.TypeName));
                }
                node.TypeName = node.Rhs.TypeName;
            }
            this.PostWalk(node);
            return false;
        }

        public override void PostWalk(ValDefStatement node)
        {
            node.ScalaVariable.TypeName = node.TypeName;
            //if (node.TypeName == null || node.TypeName == "null") throw new Exception("The type of the val has not been determined properly");
            base.PostWalk(node);
        }


        public override bool Walk(BlockStatement node)
        {
            node.Scope = _currentScope;
            if (node.Stats != null)
            {
                foreach (Statement s in node.Stats) { s.Walk(this); }
            }
            if (node.Expr != null) node.Expr.Walk(this);

            if (node.Expr is ConstantExpression) node.TypeName = node.Expr.TypeName;
            else if (node.Expr is NameExpression) node.TypeName = node.Expr.TypeName;
            else node.TypeName = "Unit";

            PostWalk(node);
            return false;
        }

        public override void PostWalk(BlockStatement node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(Parameter node)
        {
            node.Scope = _currentScope; 
            var variable = _currentScope.EnsureVariable(node.Name,node);
            if (_currentScope.IsClass || _currentScope.IsModule)
            {
                variable.IsProperty = true;
            }
            else
            {
                variable.IsProperty = false;
            }
            node.ScalaVariable = variable;
            variable.IsParam = true;
            variable.TypeName = node.TypeName;
            return base.Walk(node);
        }

        public override void PostWalk(Parameter node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(NameExpression node)
        {
            node.Scope = _currentScope;
            ScalaVariable variable;
            if (!_currentScope.TryGetAnyVariable(node.Name, out variable))
            {
                _currentScope.NameCheckList.Add(node.Name);
            }
            if (variable != null)
            {
                node.TypeName = variable.TypeName;
            }
            return base.Walk(node);
        }

        public override void PostWalk(NameExpression node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(ExpressionStatement node)
        {
            node.Scope = _currentScope;
            node.TypeName = node.Expression.TypeName;
            return base.Walk(node);
        }

        public override void PostWalk(ExpressionStatement node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(ApplyExpression node)
        {
            node.Scope = _currentScope;

            if (node.Target != null)
            {
                node.Target.Walk(this);
            }
            if (node.Args != null)
            {
                foreach (Arg arg in node.Args)
                {
                    arg.Walk(this);
                }
            }
            node.TypeName = node.Target.TypeName;

            return false;
        }

        public override void PostWalk(ApplyExpression node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(Arg node)
        {
            node.Scope = _currentScope;
            return base.Walk(node);
        }

        public override void PostWalk(Arg node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(NewStatement node)
        {
            node.Scope = _currentScope;
            node.TypeName = node.InitType;
            return base.Walk(node);
        }

        public override void PostWalk(NewStatement node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(SelectStatement node)
        {
            node.Scope = _currentScope;
            return base.Walk(node);
        }

        public override void PostWalk(SelectStatement node)
        {
            base.PostWalk(node);
        }
        #endregion
    }
}
